{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# NumPy Operations\n",
    "\n",
    "### Dr. Tirthajyoti Sarkar, Fremont, CA, 94536.\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Vectors and matrices"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Type/Class of this object: <class 'numpy.ndarray'>\n",
      "Here is the vector\n",
      "--------------------\n",
      " [1 2 3]\n"
     ]
    }
   ],
   "source": [
    "my_list = [1,2,3]\n",
    "import numpy as np\n",
    "arr = np.array(my_list)\n",
    "print(\"Type/Class of this object:\",type(arr))\n",
    "print(\"Here is the vector\\n--------------------\\n\",arr)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Type/Class of this object: <class 'numpy.ndarray'>\n",
      "Here is the matrix\n",
      "----------\n",
      " [[1 2 3]\n",
      " [4 5 6]\n",
      " [7 8 9]] \n",
      "----------\n",
      "Dimension of this matrix: 2\n",
      "Size of this matrix: 9\n",
      "Shape of this matrix: (3, 3)\n",
      "Data type of this matrix: int32\n",
      "Data type of the modified matrix: float64\n",
      "\n",
      "\n",
      "Even tuples can be converted to ndarrays...\n",
      "We write b = np.array([(1.5,2,3), (4,5,6)])\n",
      "Matrix made from tuples, not lists\n",
      "---------------------------------------\n",
      "[[ 1.5  2.   3. ]\n",
      " [ 4.   5.   6. ]]\n"
     ]
    }
   ],
   "source": [
    "my_mat = [[1,2,3],[4,5,6],[7,8,9]]\n",
    "mat = np.array(my_mat)\n",
    "print(\"Type/Class of this object:\",type(mat))\n",
    "print(\"Here is the matrix\\n----------\\n\",mat,\"\\n----------\")\n",
    "print(\"Dimension of this matrix: \",mat.ndim,sep='') #ndim gives the dimensison, 2 for a matrix, 1 for a vector\n",
    "print(\"Size of this matrix: \", mat.size,sep='') #size gives the total number of elements\n",
    "print(\"Shape of this matrix: \", mat.shape,sep='') #shape gives the number of elements along each axes (dimension)\n",
    "print(\"Data type of this matrix: \", mat.dtype,sep='') #dtype gives the data type contained in the array\n",
    "\n",
    "my_mat = [[1.1,2,3],[4,5.2,6],[7,8.3,9]]\n",
    "mat = np.array(my_mat)\n",
    "print(\"Data type of the modified matrix: \", mat.dtype,sep='') #dtype gives the data type contained in the array\n",
    "print(\"\\n\\nEven tuples can be converted to ndarrays...\")\n",
    "\n",
    "b = np.array([(1.5,2,3), (4,5,6)])\n",
    "print(\"We write b = np.array([(1.5,2,3), (4,5,6)])\")\n",
    "print(\"Matrix made from tuples, not lists\\n---------------------------------------\")\n",
    "print(b)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 'arange' and 'linspace'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "A series of numbers: [ 5  6  7  8  9 10 11 12 13 14 15]\n"
     ]
    }
   ],
   "source": [
    "print(\"A series of numbers:\",np.arange(5,16)) # A series of numbers from low to high"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numbers spaced apart by 2: [ 0  2  4  6  8 10]\n"
     ]
    }
   ],
   "source": [
    "print(\"Numbers spaced apart by 2:\",np.arange(0,11,2)) # Numbers spaced apart by 2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numbers spaced apart by float: [  0.    2.5   5.    7.5  10. ]\n"
     ]
    }
   ],
   "source": [
    "print(\"Numbers spaced apart by float:\",np.arange(0,11,2.5)) # Numbers spaced apart by 2.5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Every 5th number from 50 in reverse order\n",
      " [50 45 40 35 30 25 20 15 10  5  0]\n"
     ]
    }
   ],
   "source": [
    "print(\"Every 5th number from 50 in reverse order\\n\",np.arange(50,-1,-5))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "21 linearly spaced numbers between 1 and 5\n",
      "--------------------------------------------\n",
      "[ 1.   1.2  1.4  1.6  1.8  2.   2.2  2.4  2.6  2.8  3.   3.2  3.4  3.6  3.8\n",
      "  4.   4.2  4.4  4.6  4.8  5. ]\n"
     ]
    }
   ],
   "source": [
    "print(\"21 linearly spaced numbers between 1 and 5\\n--------------------------------------------\")\n",
    "print(np.linspace(1,5,21))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Zeroes, Ones, empty, and Identity matrix"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vector of zeroes\n",
      "---------------------\n",
      "[ 0.  0.  0.  0.  0.]\n",
      "Matrix of zeroes\n",
      "--------------------\n",
      "[[ 0.  0.  0.  0.]\n",
      " [ 0.  0.  0.  0.]\n",
      " [ 0.  0.  0.  0.]]\n"
     ]
    }
   ],
   "source": [
    "print(\"Vector of zeroes\\n---------------------\")\n",
    "print(np.zeros(5))\n",
    "print(\"Matrix of zeroes\\n--------------------\")\n",
    "print(np.zeros((3,4))) # Notice Tuples"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Vector of ones\n",
      "---------------------\n",
      "[ 1.  1.  1.  1.  1.]\n",
      "Matrix of ones\n",
      "---------------------\n",
      "[[ 1.  1.]\n",
      " [ 1.  1.]\n",
      " [ 1.  1.]\n",
      " [ 1.  1.]\n",
      " [ 1.  1.]]\n",
      "Matrix of 5's\n",
      "---------------------\n",
      "[[ 5.  5.  5.  5.  5.]\n",
      " [ 5.  5.  5.  5.  5.]\n",
      " [ 5.  5.  5.  5.  5.]]\n"
     ]
    }
   ],
   "source": [
    "print(\"Vector of ones\\n---------------------\")\n",
    "print(np.ones(5))\n",
    "print(\"Matrix of ones\\n---------------------\")\n",
    "print(np.ones((5,2))) # Note matrix dimension specified by Tuples\n",
    "print(\"Matrix of 5's\\n---------------------\")\n",
    "print(5*np.ones((3,5)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Empty matrix\n",
      "-------------\n",
      " [[ 0.  0.  0.  0.  0.]\n",
      " [ 0.  0.  0.  0.  0.]\n",
      " [ 0.  0.  0.  0.  0.]]\n"
     ]
    }
   ],
   "source": [
    "print(\"Empty matrix\\n-------------\\n\", np.empty((3,5)))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Identity matrix of dimension (4, 4)\n",
      "[[ 1.  0.  0.  0.]\n",
      " [ 0.  1.  0.  0.]\n",
      " [ 0.  0.  1.  0.]\n",
      " [ 0.  0.  0.  1.]]\n"
     ]
    }
   ],
   "source": [
    "mat1 = np.eye(4) \n",
    "print(\"Identity matrix of dimension\", mat1.shape)\n",
    "print(mat1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Random number generation"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Random number generation (from Uniform distribution)\n",
      "[[ 0.25506558  0.75756507  0.55128715]\n",
      " [ 0.04533328  0.7999211   0.81843587]]\n"
     ]
    }
   ],
   "source": [
    "print(\"Random number generation (from Uniform distribution)\")\n",
    "print(np.random.rand(2,3)) # 2 by 3 matrix with random numbers ranging from 0 to 1, Note no Tuple is necessary "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Numbers from Normal distribution with zero mean and standard deviation 1 i.e. standard normal\n",
      "[[-1.01557146 -0.57295568  1.93753693]\n",
      " [ 1.24708078 -0.78489921  1.43625099]\n",
      " [ 0.90337139 -2.06767273 -2.17374497]\n",
      " [-0.04516691 -0.6602149  -0.03035842]]\n"
     ]
    }
   ],
   "source": [
    "print(\"Numbers from Normal distribution with zero mean and standard deviation 1 i.e. standard normal\")\n",
    "print(np.random.randn(4,3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Random integer vector: [81 10 78 60 57  1  1 54 36 29]\n",
      "\n",
      "Random integer matrix\n",
      "[[72 81 22 86]\n",
      " [66 46 44 74]\n",
      " [53 15  2 11]\n",
      " [32 92 42 73]]\n",
      "\n",
      "20 samples drawn from a dice throw: [1 6 3 2 4 4 6 2 6 5 2 6 5 5 4 2 1 3 3 5]\n"
     ]
    }
   ],
   "source": [
    "print(\"Random integer vector:\",np.random.randint(1,100,10)) #randint (low, high, # of samples to be drawn)\n",
    "print (\"\\nRandom integer matrix\")\n",
    "print(np.random.randint(1,100,(4,4))) #randint (low, high, # of samples to be drawn in a tuple to form a matrix)\n",
    "print(\"\\n20 samples drawn from a dice throw:\",np.random.randint(1,7,20)) # 20 samples drawn from a dice throw"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Reshaping, min, max, sort"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Shape of a: (30,)\n",
      "Shape of b: (2, 3, 5)\n",
      "Shape of c: (6, 5)\n",
      "\n",
      "a looks like\n",
      " -------------------- \n",
      " [87 73 64 71 61 10 15 25 88 56 53  7 67 36 13 60 41 22 70 72 67 82 27 11 90\n",
      " 36 44 23 50 76] \n",
      " --------------------\n",
      "\n",
      "b looks like\n",
      " -------------------- \n",
      " [[[87 73 64 71 61]\n",
      "  [10 15 25 88 56]\n",
      "  [53  7 67 36 13]]\n",
      "\n",
      " [[60 41 22 70 72]\n",
      "  [67 82 27 11 90]\n",
      "  [36 44 23 50 76]]] \n",
      " --------------------\n",
      "\n",
      "c looks like\n",
      " -------------------- \n",
      " [[87 73 64 71 61]\n",
      " [10 15 25 88 56]\n",
      " [53  7 67 36 13]\n",
      " [60 41 22 70 72]\n",
      " [67 82 27 11 90]\n",
      " [36 44 23 50 76]] \n",
      " --------------------\n",
      "\n",
      "Vector of random integers\n",
      " -------------------------------------------------- \n",
      " [62 21 24 40 54 41 28 38 89 63]\n",
      "\n",
      "Here is the sorted vector\n",
      " -------------------------------------------------- \n",
      " [21 24 28 38 40 41 54 62 63 89]\n",
      "\n",
      "5x5 Matrix of random integers\n",
      " -------------------------------------------------- \n",
      " [[84 76 60 34 65]\n",
      " [49 27 96 16 44]\n",
      " [19  1 49 32 77]\n",
      " [94 84 23 59 37]\n",
      " [58 46 89 79 57]]\n",
      "\n",
      "Here is the sorted matrix along each row\n",
      " -------------------------------------------------- \n",
      " [[34 60 65 76 84]\n",
      " [16 27 44 49 96]\n",
      " [ 1 19 32 49 77]\n",
      " [23 37 59 84 94]\n",
      " [46 57 58 79 89]]\n",
      "\n",
      "Here is the sorted matrix along each column\n",
      " -------------------------------------------------- \n",
      " [[19  1 23 16 37]\n",
      " [49 27 49 32 44]\n",
      " [58 46 60 34 57]\n",
      " [84 76 89 59 65]\n",
      " [94 84 96 79 77]]\n"
     ]
    }
   ],
   "source": [
    "from numpy.random import randint as ri\n",
    "a = ri(1,100,30)\n",
    "b = a.reshape(2,3,5)\n",
    "c = a.reshape(6,5)\n",
    "print (\"Shape of a:\", a.shape)\n",
    "print (\"Shape of b:\", b.shape)\n",
    "print (\"Shape of c:\", c.shape)\n",
    "print(\"\\na looks like\\n\",'-'*20,\"\\n\",a,\"\\n\",'-'*20)\n",
    "print(\"\\nb looks like\\n\",'-'*20,\"\\n\",b,\"\\n\",'-'*20)\n",
    "print(\"\\nc looks like\\n\",'-'*20,\"\\n\",c,\"\\n\",'-'*20)\n",
    "\n",
    "A = ri(1,100,10) # Vector of random interegrs\n",
    "print(\"\\nVector of random integers\\n\",'-'*50,\"\\n\",A)\n",
    "print(\"\\nHere is the sorted vector\\n\",'-'*50,\"\\n\",np.sort(A, kind='mergesort'))\n",
    "\n",
    "M = ri(1,100,25).reshape(5,5) # Matrix of random interegrs\n",
    "print(\"\\n5x5 Matrix of random integers\\n\",'-'*50,\"\\n\",M)\n",
    "print(\"\\nHere is the sorted matrix along each row\\n\",'-'*50,\"\\n\",np.sort(M, kind='mergesort')) # Default axis =1\n",
    "print(\"\\nHere is the sorted matrix along each column\\n\",'-'*50,\"\\n\",np.sort(M, axis=0, kind='mergesort'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Max of a: 90\n",
      "Max of b: 90\n",
      "Max of a location: 24\n",
      "Max of b location: 24\n",
      "Max of c location: 24\n"
     ]
    }
   ],
   "source": [
    "print(\"Max of a:\", a.max())\n",
    "print(\"Max of b:\", b.max())\n",
    "print(\"Max of a location:\", a.argmax())\n",
    "print(\"Max of b location:\", b.argmax())\n",
    "print(\"Max of c location:\", b.argmax())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Indexing and slicing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Array: [ 0  1  2  3  4  5  6  7  8  9 10]\n",
      "Element at 7th index is: 7\n",
      "Elements from 3rd to 5th index are: [3 4 5]\n",
      "Elements up to 4th index are: [0 1 2 3]\n",
      "Elements from last backwards are: [10  9  8  7  6  5  4  3  2  1  0]\n",
      "3 Elements from last backwards are: [10  8  6]\n",
      "New array: [ 0  2  4  6  8 10 12 14 16 18 20]\n",
      "Elements at 2nd, 4th, and 9th index are: [ 4  8 18]\n"
     ]
    }
   ],
   "source": [
    "arr = np.arange(0,11)\n",
    "print(\"Array:\",arr)\n",
    "print(\"Element at 7th index is:\", arr[7])\n",
    "print(\"Elements from 3rd to 5th index are:\", arr[3:6])\n",
    "print(\"Elements up to 4th index are:\", arr[:4])\n",
    "print(\"Elements from last backwards are:\", arr[-1::-1])\n",
    "print(\"3 Elements from last backwards are:\", arr[-1:-6:-2])\n",
    "\n",
    "arr = np.arange(0,21,2)\n",
    "print(\"New array:\",arr)\n",
    "print(\"Elements at 2nd, 4th, and 9th index are:\", arr[[2,4,9]]) # Pass a list as a index to subset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix of random 2-digit numbers\n",
      "--------------------------------\n",
      " [[66 78 75 78 32]\n",
      " [53 42 94 92 99]\n",
      " [96 98 12 40 49]]\n",
      "\n",
      "Double bracket indexing\n",
      "------------------------\n",
      "Element in row index 1 and column index 2: 94\n",
      "\n",
      "Single bracket with comma indexing\n",
      "----------------------------------\n",
      "Element in row index 1 and column index 2: 94\n",
      "\n",
      "Row or column extract\n",
      "----------------------\n",
      "Entire row at index 2: [96 98 12 40 49]\n",
      "Entire column at index 3: [78 92 40]\n",
      "\n",
      "Subsetting sub-matrices\n",
      "--------------------------\n",
      "Matrix with row indices 1 and 2 and column indices 3 and 4\n",
      " [[92 99]\n",
      " [40 49]]\n",
      "Matrix with row indices 0 and 1 and column indices 1 and 3\n",
      " [[78 78]\n",
      " [42 92]]\n"
     ]
    }
   ],
   "source": [
    "mat = np.array(ri(10,100,15)).reshape(3,5)\n",
    "print(\"Matrix of random 2-digit numbers\\n--------------------------------\\n\",mat)\n",
    "\n",
    "print(\"\\nDouble bracket indexing\\n------------------------\")\n",
    "print(\"Element in row index 1 and column index 2:\", mat[1][2])\n",
    "\n",
    "print(\"\\nSingle bracket with comma indexing\\n----------------------------------\")\n",
    "print(\"Element in row index 1 and column index 2:\", mat[1,2])\n",
    "print(\"\\nRow or column extract\\n----------------------\")\n",
    "\n",
    "print(\"Entire row at index 2:\", mat[2])\n",
    "print(\"Entire column at index 3:\", mat[:,3])\n",
    "\n",
    "print(\"\\nSubsetting sub-matrices\\n--------------------------\")\n",
    "print(\"Matrix with row indices 1 and 2 and column indices 3 and 4\\n\", mat[1:3,3:5])\n",
    "print(\"Matrix with row indices 0 and 1 and column indices 1 and 3\\n\", mat[0:2,[1,3]])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Conditional subsetting"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Matrix of random 2-digit numbers\n",
      "--------------------------------\n",
      " [[27 13 20 56 86]\n",
      " [32 99 90 66 79]\n",
      " [66 44 22 57 87]]\n",
      "Elements greater than 50\n",
      " [56 86 99 90 66 79 66 57 87]\n"
     ]
    }
   ],
   "source": [
    "mat = np.array(ri(10,100,15)).reshape(3,5)\n",
    "print(\"Matrix of random 2-digit numbers\\n--------------------------------\\n\",mat)\n",
    "print (\"Elements greater than 50\\n\", mat[mat>50])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Slicing keeps the original reference, be aware of mutating the original array"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 39,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original matrix\n",
      "[[11 12 13]\n",
      " [21 22 23]\n",
      " [31 32 33]]\n",
      "\n",
      "Sliced matrix\n",
      "[[11 12]\n",
      " [21 22]]\n",
      "\n",
      "Change the sliced matrix\n",
      "[[1000   12]\n",
      " [  21   22]]\n",
      "\n",
      "But the original matrix? WHOA! It got changed too!\n",
      "[[1000   12   13]\n",
      " [  21   22   23]\n",
      " [  31   32   33]]\n",
      "\n",
      "Doing it again little differently now...\n",
      "\n",
      "Original matrix\n",
      "[[11 12 13]\n",
      " [21 22 23]\n",
      " [31 32 33]]\n",
      "\n",
      "Sliced matrix\n",
      "[[11 12]\n",
      " [21 22]]\n",
      "\n",
      "Change the sliced matrix\n",
      "[[1000   12]\n",
      " [  21   22]]\n",
      "\n",
      "But the original matrix? NO CHANGE this time:)\n",
      "[[11 12 13]\n",
      " [21 22 23]\n",
      " [31 32 33]]\n"
     ]
    }
   ],
   "source": [
    "mat = np.array([[11,12,13],[21,22,23],[31,32,33]])\n",
    "print(\"Original matrix\")\n",
    "print(mat)\n",
    "mat_slice = mat[:2,:2]\n",
    "print (\"\\nSliced matrix\")\n",
    "print(mat_slice)\n",
    "print (\"\\nChange the sliced matrix\")\n",
    "mat_slice[0,0] = 1000\n",
    "print (mat_slice)\n",
    "print(\"\\nBut the original matrix? WHOA! It got changed too!\")\n",
    "print(mat)\n",
    "\n",
    "# Little different way to create a copy of the slixed matrix\n",
    "print (\"\\nDoing it again little differently now...\\n\")\n",
    "mat = np.array([[11,12,13],[21,22,23],[31,32,33]])\n",
    "print(\"Original matrix\")\n",
    "print(mat)\n",
    "mat_slice = np.array(mat[:2,:2]) # Notice the np.array command to create a new array not just slicing\n",
    "print (\"\\nSliced matrix\")\n",
    "print(mat_slice)\n",
    "print (\"\\nChange the sliced matrix\")\n",
    "mat_slice[0,0] = 1000\n",
    "print (mat_slice)\n",
    "print(\"\\nBut the original matrix? NO CHANGE this time:)\")\n",
    "print(mat)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Array operations (array-array, array-scalar, universal functions)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "1st Matrix of random single-digit numbers\n",
      "----------------------------------------\n",
      " [[3 7 1]\n",
      " [7 8 6]\n",
      " [3 6 9]]\n",
      "\n",
      "2nd Matrix of random single-digit numbers\n",
      "----------------------------------------\n",
      " [[8 2 7]\n",
      " [7 5 1]\n",
      " [9 5 6]]\n",
      "\n",
      "Addition\n",
      "------------------\n",
      " [[11  9  8]\n",
      " [14 13  7]\n",
      " [12 11 15]]\n",
      "\n",
      "Multiplication\n",
      "------------------\n",
      " [[24 14  7]\n",
      " [49 40  6]\n",
      " [27 30 54]]\n",
      "\n",
      "Division\n",
      "------------------\n",
      " [[ 0.375       3.5         0.14285714]\n",
      " [ 1.          1.6         6.        ]\n",
      " [ 0.33333333  1.2         1.5       ]]\n",
      "\n",
      "Lineaer combination: 3*A - 2*B\n",
      "-----------------------------\n",
      " [[ -7  17 -11]\n",
      " [  7  14  16]\n",
      " [ -9   8  15]]\n",
      "\n",
      "Addition of a scalar (100)\n",
      "-------------------------\n",
      " [[103 107 101]\n",
      " [107 108 106]\n",
      " [103 106 109]]\n",
      "\n",
      "Exponentiation, matrix cubed here\n",
      "----------------------------------------\n",
      " [[ 27 343   1]\n",
      " [343 512 216]\n",
      " [ 27 216 729]]\n",
      "\n",
      "Exponentiation, sq-root using pow function\n",
      "-------------------------------------------\n",
      " [[ 1.73205081  2.64575131  1.        ]\n",
      " [ 2.64575131  2.82842712  2.44948974]\n",
      " [ 1.73205081  2.44948974  3.        ]]\n"
     ]
    }
   ],
   "source": [
    "mat1 = np.array(ri(1,10,9)).reshape(3,3)\n",
    "mat2 = np.array(ri(1,10,9)).reshape(3,3)\n",
    "print(\"\\n1st Matrix of random single-digit numbers\\n----------------------------------------\\n\",mat1)\n",
    "print(\"\\n2nd Matrix of random single-digit numbers\\n----------------------------------------\\n\",mat2)\n",
    "\n",
    "print(\"\\nAddition\\n------------------\\n\", mat1+mat2)\n",
    "print(\"\\nMultiplication\\n------------------\\n\", mat1*mat2)\n",
    "print(\"\\nDivision\\n------------------\\n\", mat1/mat2)\n",
    "print(\"\\nLineaer combination: 3*A - 2*B\\n-----------------------------\\n\", 3*mat1-2*mat2)\n",
    "\n",
    "print(\"\\nAddition of a scalar (100)\\n-------------------------\\n\", 100+mat1)\n",
    "\n",
    "print(\"\\nExponentiation, matrix cubed here\\n----------------------------------------\\n\", mat1**3)\n",
    "print(\"\\nExponentiation, sq-root using pow function\\n-------------------------------------------\\n\",pow(mat1,0.5))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Broadcasting (super cool feature)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "NumPy operations are usually done on pairs of arrays on an element-by-element basis. In the simplest case, the two arrays must have exactly the same shape.\n",
    "\n",
    "NumPy’s broadcasting rule relaxes this constraint when the arrays’ shapes meet certain constraints. When operating on two arrays, NumPy compares their shapes element-wise. It starts with the trailing dimensions, and works its way forward. Two dimensions are compatible when\n",
    "\n",
    "* **they are equal, or**\n",
    "* **one of them is 1**\n",
    "\n",
    "If these conditions are not met, a *ValueError: frames* are not aligned exception is thrown, indicating that the arrays have incompatible shapes. The size of the resulting array is the maximum size along each dimension of the input arrays.\n",
    "\n",
    "For more detail, please look up: https://docs.scipy.org/doc/numpy-1.10.1/user/basics.broadcasting.html\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 47,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 0.  0.  0.]\n",
      " [ 0.  0.  0.]\n",
      " [ 0.  0.  0.]\n",
      " [ 0.  0.  0.]]\n"
     ]
    }
   ],
   "source": [
    "start = np.zeros((4,3))\n",
    "print(start)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 48,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[1 0 2]\n"
     ]
    }
   ],
   "source": [
    "# create a rank 1 ndarray with 3 values\n",
    "add_rows = np.array([1, 0, 2])\n",
    "print(add_rows)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 49,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 1.  0.  2.]\n",
      " [ 1.  0.  2.]\n",
      " [ 1.  0.  2.]\n",
      " [ 1.  0.  2.]]\n"
     ]
    }
   ],
   "source": [
    "y = start + add_rows  # add to each row of 'start' using broadcasting\n",
    "print(y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 50,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[0]\n",
      " [1]\n",
      " [2]\n",
      " [3]]\n"
     ]
    }
   ],
   "source": [
    "# create an ndarray which is 4 x 1 to broadcast across columns\n",
    "add_cols = np.array([[0,1,2,3]])\n",
    "add_cols = add_cols.T\n",
    "print(add_cols)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 51,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 0.  0.  0.]\n",
      " [ 1.  1.  1.]\n",
      " [ 2.  2.  2.]\n",
      " [ 3.  3.  3.]]\n"
     ]
    }
   ],
   "source": [
    "# add to each column of 'start' using broadcasting\n",
    "y = start + add_cols \n",
    "print(y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 100.  100.  100.]\n",
      " [ 100.  100.  100.]\n",
      " [ 100.  100.  100.]\n",
      " [ 100.  100.  100.]]\n"
     ]
    }
   ],
   "source": [
    "# this will just broadcast in both dimensions\n",
    "add_scalar = np.array([100])  \n",
    "print(start+add_scalar)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### NumPy mathematical functions on array"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 41,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "1st Matrix of random single-digit numbers\n",
      "----------------------------------------\n",
      " [[2 6 4]\n",
      " [3 7 3]\n",
      " [2 4 2]]\n",
      "\n",
      "2nd Matrix of random single-digit numbers\n",
      "----------------------------------------\n",
      " [[8 2 8]\n",
      " [3 9 6]\n",
      " [1 5 4]]\n",
      "\n",
      "Sq-root of 1st matrix using np\n",
      "------------------\n",
      " [[ 1.41421356  2.44948974  2.        ]\n",
      " [ 1.73205081  2.64575131  1.73205081]\n",
      " [ 1.41421356  2.          1.41421356]]\n",
      "\n",
      "Exponential power of 1st matrix using np\n",
      " -------------------------------------------------- \n",
      " [[    7.3890561    403.42879349    54.59815003]\n",
      " [   20.08553692  1096.63315843    20.08553692]\n",
      " [    7.3890561     54.59815003     7.3890561 ]]\n",
      "\n",
      "10-base logarithm on 1st matrix using np\n",
      " -------------------------------------------------- \n",
      " [[ 0.30103     0.77815125  0.60205999]\n",
      " [ 0.47712125  0.84509804  0.47712125]\n",
      " [ 0.30103     0.60205999  0.30103   ]]\n",
      "\n",
      "Modulo reminder using np\n",
      " -------------------------------------------------- \n",
      " [[2 0 4]\n",
      " [0 7 3]\n",
      " [0 4 2]]\n",
      "\n",
      "Combination of functions by shwoing exponetial decay of a sine wave\n",
      " ----------------------------------------------------------------------\n"
     ]
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXwAAAEICAYAAABcVE8dAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJztnXucHXV5/99PNpuQhECSEiIsCcEYoVwiq1sujZdQSUER\nWbUq/sBitWBb20pjg4HkV6AlP2NRiv1ZtaFasES5CCyxKHRFsRUhGNxI5CagEFgDCYYACZjr0z/m\nOzA5O7dzZubM7Ozzfr32lTO3M8+ZnPOZ73y+z/f5iqpiGIZh1J9RZQdgGIZhtAcTfMMwjBGCCb5h\nGMYIwQTfMAxjhGCCbxiGMUIwwTcMwxghmODXHBHZIiKvTbHfTBFRERmd8n2vFJFLskdYPCIyTkS+\nLSLPi8j1TRw3w12/jiLjG2mIyDdFpLfFY+8RkSMynj/VbyKwf6p4RWSOiPw4S2xFM6IFX0QeF5GX\n3RfA//ti2XG1iojcISJ/Glynqnur6i/Liqki/BEwDfgdVX1/2oNUdZ27fruKC21kISJzgDcAN7f4\nFp8D/j7kfa9M+wbN/CaaiVdV7wM2i8ipaWNpNyNa8B2nui+A//eXZQdk5M7BwC9UdWfZgRh8HFih\nrY/4XAmcICKvEY+viMjBACKyn4gsF5HxuUXbfLwr3DHVRFVH7B/wOHBixLYvAzcElj8L3A4IMA94\nCrgAeNa9zxmBffcFvg5sBJ4AlgCj3LaPAD/Ca6k8B/wKeEfDsV8F1gODwCVAR9KxwFJgF/BbYAvw\nRbdegde516cAA8ALwJPARYHzznT7jo64Ht3AT4EXgWuBa4BLAtvfBawBNgM/BuYEtk0HbnTX4zeB\n2GYB33frnsX7sUxy2xYGr79b98/AFyLi+13gDnf++4F3u/UXA9uBHe66fCzk2GOA1e66PANcFnZN\n3Pv/A3Cnuw7/BewXeJ/j3GffDPwMmBcR658A3w4sPwJcH1h+Ejjavf6CW34BuBd4i1t/IPAyMKXh\n/+hZoNMtfxR40H1XbgMObuK3EfpZgCl43/1T3fLewKPAH7vlK4GvAP3uGv0weF7gl8CbY877++4z\nTHfLb3DxHxbYpx84y70+2J3zMeA63PcOeJ079/Pu/a4NHB/8TVwJ/Atwi4t3FTArKl5idMEtd7n/\nl7FlaFri/2vZAZT64eMFfzzwCzyRfYv70hzkts0DdgKXAWOBtwFbgUPd9q/jPQJOxBONX+CExr3f\nDuBsoAP4c+DXgS/MTcC/AhOA/YF7gI+nPPYO4E8bPkfwyz0POArvyW4Onrj1um0ziRB8YAzejetv\ngE48i2QHTvDxhGYDcKyL6yx3bce65Z8B/+Q+017+D8j9KOe7/aYC/w1c7rYd4K6pfwMY7c7xppD4\nOvFE5wIX6x/g/Xj9/4+LgKtjvgd3AR92r/cGjgu7Ju76Pga8Hhjnlpe5bV14N653uus73y1PDTnf\na/GEdBSecD8BPBXY9hyvNhDOBH7Hff5PAU8De7lt3wfODrzvpcBX3OvT3DX5XXfsEuDHgX3/E1gU\ncT1iPwvwhy6O/YErgG8Fjr3SXfu3uv/XLwA/ctsmuOs55Jo0nH+p+2zjgLXAXzZs/2devSkfDPw7\nnjBfCxzp1n8TWOzif+U7F/KbuNJ9tmPcdVoBXBMVLzG6ENjnBQINnir9lR5AqR/eE6Ut7sfn/wV/\nQMcCm9wP8kOB9fPwBH9CYN11wP/FE7jtwOGBbR8H7nCvPwI82vAFUuA1eD7zNmBcYPuHgB8kHeuW\n7yBG8EM+/+XAP7nXM4kW/LcSuLG4dT/mVcH/MvAPDcc8jHcjPB6vZR/65NBwTC8wEFj+rv//gfcE\n8UDEcW/BE6BRgXXfxD3BkCz4/433JLBfw/o9rom7vksC2/8CuNW9/jTwHw3H34ZriYac80ngjcDp\nwHK8G/theK3/lTGxPge8wb3+U+D77rW493xr4Np9LHDcKOAlUrTy03wW4P/jifEgXt+Iv/5KnGC6\n5b3xnjyn491IFHfDijl/J97TzFrg1uD3zm1fCnzNfeav8Gorfz93LcfjNbqW0yDGjb8Jd9y/Bba9\nE3jIvQ6NlwhdCGwf9P8fqvZnHr7Xwp0U+LvC36Cqq/BaDoIn6EGeU9WtgeUn8Fpr++F9YZ9o2NYV\nWH46cI6X3Mu98b64ncB6EdksIpvxWvv7pzg2ERE5VkR+ICIbReR54M9cvEkcCAyq+zYHPpPPwcCn\n/Jhd3NPdcdOBJzTEPxeRaSJyjYgMisgLwNUN8VyF18LF/fsfMfE9qaq7G+Lriti/kY/htdofEpGf\niMi7YvZ9OvD6JV699gcD72+4Bm/Ge1IJ44d4DYe3utd34N0g3+aWARCRvxWRB12G0WY8y8+/RjcA\nx4vIAe59dgP/E4jnC4FYNuF9j9NckzSfZTlwJHClqv6m4fgn/RequsWd+0C8BhV4T77+57s/kDDx\nFnfMDjwhPhL4fMP3zj9+s3r8mao+4Y57VlXPcb+L89znvced46Mxnzfq/3RIvO48cbrwSnwx5ysN\nE/wYROQTeI+lv8b7AgWZLCITAssz3H7P4tkdBzdsG0xxyifxWvj7BW5A+6hq2jS0xh9GI9/A6/Sa\nrqr74rWOJMX7rge6RCS474yGuJc23DjHq+o33bYZEeme/8/FfJSq7oMn6sFz9AFzRORIvBb+ioj4\nfg1MF5Hg9zntNUdVH1HVD+HdWD8LfKvh/zYNT+K1ioPXYIKqLovY3xf8t7jXP6RB8J0Angd8AJis\nqpPwPGlxcT+H14/wQeD/4LWs/e/Ak3hWYDCecaqaJm0w9rO4NNXleK3ovxCR1zUcP91/ISJ74/n+\nv3YNJN8Sw32GI/TVhIn/ccd0ARfiWTWfF5GxDe//u3g2IYH3+UjD8tOqeraqHoj3hP2lkDhjCYvX\nxRepCy72MXhPuJXDBD8CEXk9XofpmcCHgfNE5OiG3S4WkTHuh/kuvI63XXh3/aUiMtFlECzAa73G\noqrr8X7AnxeRfURklIjMEpG3pQz7GTwPOIqJwCZV/a2IHIMnEmm4C8/C+msR6RSR9+J5nj5XAH/m\nniBERCaIyCkiMhHPqlgPLHPr9xKRuYF4tgDPux/KwuBJVfW3wLfwblT3qOq6iPhW4bXMznPxzQNO\nxetYTkREzhSRqe4JwW+Z7Y47JoSrgVNF5CQR6XCfc56IHBSx/w+BE/Dsu6fwWuYn4/n1A26fiXjX\nfSMwWkT+Dtin4X2+AfwxXr/KNwLrvwKc7+esi8i+IpI2JTXps1yAd6P+KF6/wddlz7EK7xSRN4vI\nGLxO7rtV1W/1fwfvphaKa1RciZe48DG8784/BLbvBbwJr+M2EhF5fyDe51y8zf6fDok3hS68Dc9m\n29bCuYqnbE+pzD88D/9lPNHx/27C67y5h0CnFl4H6Vq8O/s8vEyFxXgt+nW4Tj+372S8H81GvNbS\n39GQpdMQR9BT3BfPE38KrzU3AJye8tjj8TqUngP+OWT7H+FZHS/iddp9Eedtk5yl0+Ni8bN0rmXP\nLJ2TgZ/gCeZ64Hpgots2A6+17mfj+LEdgefVbsHL8PkUrvMy8L5vdnH9ScL/5RG8mpXxAPCewLaL\niPfwr8brEN6Cl+ET2pFNQx9J4/8Hnrf7QzwLYyNe5seMmPOuB/49sLwa+G5guQPPq37B7XseDYkG\neB2bLwL3h7z/h/G+s35W1tcC274LXBATW+hnwRPb5wLfqQ68rKXFbvlKXs3S2YLXP3JI4H2PdNdY\nIs77SbzW+xi3fKA7v5+d9H7gxhS/7X/Ee8LbgtdKPyfiN3Mle36P5wW/g8F4SdAFt3wLLkOsin9+\ndofRBK4FebWqRrXejJwQkRnAQ3gd0y+UHY8RjxsA9ZSqLonZ5xvAdara18L7r8LrjP5561E2fc5U\n8bpBWv+qqse3J7LmSTWM3jDKwHnyC/C8aRP7mqCqaa3EsGOPzTOWlOdMFa96I20rK/Zggm9UFNdp\n+gyeBXVyyeEYRi0wS8cwDGOEYFk6hmEYI4RKWTr77befzpw5s+wwDMMwhhX33nvvs6o6NWm/Sgn+\nzJkzWb16ddlhGIZhDCtE5InkvczSMQzDGDGY4BuGYYwQTPANwzBGCCb4hmEYIwQTfMMwjBFCpbJ0\n2sGSvrWsuHvdK3WEJ4zpYOl7jqK3O23pdMMwjOHJiBL8JX1rufruPSvsbt2+i3OvXQNgom8YRq0Z\nUZZOo9gHWeBE3zAMo66MGMFf0rc2dvvuFPsYhmEMZ0aM4K+Iad37xD0BGIZhDHdGhOD3DQwmTvYa\n3NcwDKOOjAjBv/S29PMJX7Ty/gIjMQzDKI8RIfiDm19Ove/ml3cUGIlhGEZ55CL4IvI3InK/iPxc\nRL7pZrmfIiL9IvKI+3dyHudqhVFS1pkNwzCqQ2bBF5Eu4K+BHlU9Em8W+9OBRcDtqjobuN0tl8Lu\nJif1Mh/fMIw6kpelMxoYJyKjgfHAr4HTgKvc9quA3pzO1RStiHcznr9hGMZwIbPgq+og8DlgHbAe\neF5V/wuYpqrr3W5PA9PCjheRc0RktYis3rhxY9ZwhnDxt5vvhG3G8zcMwxgu5GHpTMZrzR8CHAhM\nEJEzg/uoN1N6qLGiqstVtUdVe6ZOTZyhq2mee6n5Tliz/A3DqCN5WDonAr9S1Y2qugO4Efh94BkR\nOQDA/bshh3O1hSYtf8MwjGFBHoK/DjhORMaLiABvBx4EVgJnuX3OAm7O4VxNE9Vat1a8YRgjjczV\nMlV1lYh8C/gpsBMYAJYDewPXicjHgCeAD2Q9V0vxxawfJc1n8BiGYQxXcimPrKoXAhc2rN6G19ov\nlQ4RdulQVY9a79M3MGjlkg3DqBW1H2kbJeq7VOmaNC7yOEvNNAyjbtRa8PsGBiO9+q5J41h40qGR\nx/7aUjMNw6gZtRb8S297ONTDF2DhSYfS293F+M7wS7DvuM5CYzMMw2g3tRb8qAFUyqvTGY7t7Ajd\nRyyNxzCMmlFrwe+IUO3g+qiBWa0M2DIMw6gytRb8uA5bn6ibgjXwDcOoG7UW/Mnjw3344Pqom4Ji\nVTMNw6gXtRb8bTt2ha4ParylZhqGMVKoreD3DQzy0o7dodueD8xqZamZhmGMFGor+HGt8wMDrXpL\nzTQMY6RQW8GPa503tuotNdMwjJFAbQV/UkSH7fjOUUNq5FhqpmEYI4HaCn5UXbQxo4e25tPk6xuG\nYQx3aiv4wY7ZpPVp8vUNwzCGO7UV/ChLJ2x9VGqmYLn4hmHUh9oKflTjPGz9wpMODR1Zq1guvmEY\n9aG2gr+5CUunt7srcmYsy8U3DKMu1FLw4+rgHxhh30yKyLm3XHzDMOpCLoIvIpNE5Fsi8pCIPCgi\nx4vIFBHpF5FH3L+T8zhXGpLq4IcRlZBjiTqGYdSFvFr4XwBuVdXDgDcADwKLgNtVdTZwu1tuC1E2\nTLAOfiObI3Luo9YbhmEMNzILvojsC7wV+CqAqm5X1c3AacBVbrergN6s50pLVIZOVPVMiLZuzNIx\nDKMu5NHCPwTYCPy7iAyIyL+JyARgmqqud/s8DUwLO1hEzhGR1SKyeuPGjTmE01yGzqtxNLfeMAxj\nuJGH4I8G3gh8WVW7ga002DeqqhCeCKOqy1W1R1V7pk6dmkM4zQ268jFLxzCMupOH4D8FPKWqq9zy\nt/BuAM+IyAEA7t8NOZwrFVGZOFHrwSwdwzDqT2bBV9WngSdFxE9/eTvwALASOMutOwu4Oeu50nLC\nYeFPClHrwSwdwzDqz+ic3uevgBUiMgb4JfAneDeT60TkY8ATwAdyOlciP3govC8gaj1EWzdWMdMw\njLqQi+Cr6hqgJ2TT2/N4/2YZjEjLjBs1e+CkcaHH+fV0otI5DcMwhgu1G2nbyihbsHo6hmHUn9oJ\nfiujbMHq6RiGUX9qJ/itjLL1sXo6hmHUmdoJfpRtE1XzPohl6hiGUWdqJ/itpGT62OArwzDqTO0E\nv5WUTB8bfGUYRp2pneBHefhpOl7N0jEMo87UTvBbKavgY4OvDMOoM7UT/CweftRNwSYzNwyjDtRO\n8LN4+Db4yjCMOpNXLZ3KkMXD7+3u4txr17R8fDtZ0reWq+9eN2T92NGj+Oz75lgpCMMwhlC7Fn4W\nDx+i8/XTHt8OzrjirlCxB9i2czfnXrvGLCjDMIZQO8HP4uHncXzR9A0McudjmxL3W3h9+JOKYRgj\nl9oJfhYPP4/ji2bxTWtT7bdjt3U0G4axJ7UT/Cwefh7HF83W7btS73vRyvsLjMQwjOFG7QR/0vjw\nUbFpPfgqj7ZttsW+OWYOX8MwRh61Evy+gUG2/HbnkPWdHRJbGjlIlUfbXvzt5lvsZusYhuGTm+CL\nSIeIDIjIf7rlKSLSLyKPuH8n53WuKC697WF27B5a1X7CmNGp0xSrXECtlRG/ZusYhuGTZwv/k8CD\ngeVFwO2qOhu43S0XSpTP/nwT1kaU9VO2pdNqS91sHcMwfHIRfBE5CDgF+LfA6tOAq9zrq4DePM4V\nR9YcfPBG23aOGurfbN2+s1R7pBU7x8dsHcMwIL8W/uXAecDuwLppqrrevX4amBZ2oIicIyKrRWT1\nxo3ZUh/zyKHv7e5i772GDkDesUtLLa8QZ+dEzdTlY7aOYRiQg+CLyLuADap6b9Q+qqoQPmWsqi5X\n1R5V7Zk6Ndvgprxy6KP8+rJSM5Na6Be9+wgmR2Qngdk6hmF45FFLZy7wbhF5J7AXsI+IXA08IyIH\nqOp6ETkA2JDDuWLJK4d+33GdoSJZlo+f9GThd0hH1QEyDMOAHFr4qnq+qh6kqjOB04Hvq+qZwErg\nLLfbWcDNWc+VRB4ePlQvNXMw5obl2zlWLM0wjCSKzMNfBswXkUeAE91yoeRVB6dqqZkhfcivcNG7\nj0i1n3XcGoaRq+Cr6h2q+i73+jeq+nZVna2qJ6pqcsWvjOTl4ef1pJAXIUMLXiHYso/bzzpuDcOo\n1UjbvDz8qlfMjCKqtDNYx61hGDUT/Lxa5lWvmOnT6OCkLR9hGMbIpFaCf8JhU4eI4LjOjqaFMOqJ\nIK7ztCjivPdGB6e3uyt0isY072UYRv2pjeD3DQxyw72De4igAO97U1fTGSxVmsw8boRtmIUTY+Pb\nvLyGMcKpjeBfetvDvLxjz1rxSms2TJUmM48bYRv25BLn45fxhGIYRnWojeDnOXFJb3dXZEu5KhOh\nQHjufZx91VGFGs+GYZRGbQQ/71TKqk9mHiXdcfbVLo0zfAzDqDu1Efy8UymrkJrZTIdtkLiWvHXc\nGsbIpTaCn3cqZRVSM+P6C+K8+riWvHXcGsbIpTaCn/fk41WYzDyukzXOq7eOW8MwwqiN4Oft4Veh\nvEKUNSPEe/XWcWsYRhi1EfyFJx1KZ8eeYtbM5OWNVMHDj7JmkrperePWMIww8qiHXx0atSyDtlXB\nwx8l4QXR0rTShfCPX8X2/fzL7uCRDVuHrJ87aworzj6+hIgMo57UpoV/6W0Ps6NBHXfsbn1awrI9\n/L6Bwcjql2la6VF7VK19f9ji74SKPcCdj21izoW3tjkiw6gvtRH8vAW6bA+/1QydNFQlNfPYpf38\ndlf8LeiFbbs4dml/myIyjHpTG8GfFDGna6sCXbaH32qGjk/cHLdVSM0844q7eObF7an2febF7Szp\nW1twRIZRf2oh+H0Dg2z57c4h67N02pbt4beaoeNz4alHRG4rOzWzb2CQOx9rbj6cq+9eV1A0hjFy\nqIXgh/n3ABPGjG55rteySyS3mqHj09vdFTnlYdmpmYtvaq21fsYVd+UciWGMLDILvohMF5EfiMgD\nInK/iHzSrZ8iIv0i8oj7d3L2cMOJEufnM8zyVHaJ5ChRbkass3T6FsnW7buSdwrhzsc2Vab/wTCG\nI3m08HcCn1LVw4HjgE+IyOHAIuB2VZ0N3O6WC6GIDtaySyRHiXIzYh1nC5VFVi/+/BvvyykSwxh5\nZBZ8VV2vqj91r18EHgS6gNOAq9xuVwG9Wc8VRd6DrqDcEsl9A4ORotxMhk6cLVRWS3lFghc/d9aU\n2O0v79htrXzDaJFcPXwRmQl0A6uAaaq63m16GpgWccw5IrJaRFZv3JihQzTHQVc+ZZVIvvS2hyMH\nTTVzE4u7OZSRqdM3MBj739I5ClacfTyjozofHBetjJ4FzDCMaHITfBHZG7gBOFdVXwhuU1UlQoJV\ndbmq9qhqz9SpraU85j3oyqes1MyojmElXYaOT9zNoYxMnbjpGgEuff/RAHzu/W+I3W9zhr4ZwxjJ\n5CL4ItKJJ/YrVPVGt/oZETnAbT8A2JDHucIoalRsWamZeXTYQvUydeKma+wc9erNrLe7iwljOtoV\nlmGMGPLI0hHgq8CDqnpZYNNK4Cz3+izg5qzniqKoUbFllVfIo8PWpyqZOkm+u9+691n6nqNi97eB\nWIbRPHm08OcCHwb+QETWuL93AsuA+SLyCHCiWy6EIjptobzyCnm18OOOaXf7PsnOabSqkqwrG4hl\nGM2TR5bOj1RVVHWOqh7t/r6jqr9R1ber6mxVPVFVmxta2XQgCcstUJaHn2cLvyqZOnF2zqRx4WUg\n4spDQHVqAhnGcKEW5ZHjOm1bHWkL5Xn4k8d3hgpkkgCG0TVpXGQHbdbrk5YkYb7o3eFlIC489QjO\nvXZN9HEr729L/GlZ0rc28snDSj0bVaAWpRWK8trLKq+wbUf4SNRWbPcqZOo0a+cE148dHf0VrVK2\nzpwLb421me58bBOHLLrFnkqMUqmF4BfltZdRXqFvYJCXduwO3dZKqYgqZOq0Yuf4fPZ9c2K3V0FA\nD1v8HV7YllwuQoFzr11jNYGM0qiF4BfVaVtGeYW49231BlaVTJ0wouwcnyTLpuxBWHMuvDWxpn8j\ndz62yUTfKIVaCD5QSKdtGeUV4t631RtYmZk6SS3wNB58XN9FmbbOsUv7U7Xsw7BCcEYZ1ELwixpp\nC+0vrxA1kcv4zlEtd1CWmamT5N+nIa62P5Rj6yzpW5t6ApcoFsR0SBtGEdRC8IscINXu1Mwol2XM\n6NZHnpZZUyeLf+9TRVsnj3EAu/EmcDeMdlELwS9ygFS7UzOjLIos1kUVMnXCSPLvg1TJ1snTf39k\nw1YbNWy0jVrk4Z9w2FRW3L1uD799XGdH5k5baH95hQ6RUAsmS0ZNb3cXC65bE9p5W2SmTh7+vU9S\nTn67SDM94z5jO7jv4pMBrwX/yIatsftfffc6LumNLyXRbuLitjEFw5dh38LvGxjkhnv3LLsrwPve\n1JXLoJx2l1fIc5RtkDIydfLw732S/i/b5eN/+obkCVh8sQfoXzCP2ftPSDymClk7S/rWMnPRLcxc\ndEvsTerOxzYxc9EtlYjZaI5hL/iX3vYwLzcMVFLys1za6eHnNfFJGHnW50lLnH+f9fM00g4fv29g\nkG07w8dI+Jx53Iwh6/oXzEus8V921s78y+5oul/ChH/4MewFv2jLpZ0efl4Tn4RR1JNDq7Tyecr2\n8ZNa96Mg0ppJqvEP5U3feOzS/kTbKY47H9vEnAtvzTEioyiGveAXbbm008PPa+KTMNqdi5+nf+9T\nZnpmmtb9ZR88OnJbb3dXJadvnHPhrZnTSwFe2LbLSkcMA4Z9p+0Jh00NfRTNy3I5MKL42L4pUwqb\noYgOW5+kXPy8i5Dl6d/79HZ3xXbcFlkMbvFN8Zk0c2dNSTz3irOP53UXfIedUR0qeLn57SoIN+fC\nW1seOBaGXzpi9RObSu2E7hsYZOH1a4ioUBLJmcfNqFzned4M+xZ+0ZbLwpMOpTPEf926fWfurZki\nbZd25+IX5d/H2TpFpphu3R4vjGmzVpKsnd20Z3KXLKOEk7j67nWlpJr6nc7nXtu82IMX98xFt9R6\nbMSwF/yiLZfe7i723mvog9COXfmM5A0SJWatlEVupEq5+Fn6I5JsnSJIEq+wjtoo0kzfWPTkLvMv\nu6MpG2furCmpMo2CtFP0+wYGmbnoltyu2yMbtjJz0S0cuuS7tbOohr3gtyNtcnNEazVvHz+qIZ9H\nv2oVqmYGYynq2CJ+oCsShKRZGyBp+kYorj/ijCvuSt1BO3fWFB5fdgorzj6e/gXzeHzZKU0JfztE\n/9il/YWNz9i2czfnXruGY5f2F/L+ZVC44IvIySLysIg8KiKL8n7/oiplBony6/P28aMyTVopixxG\nlatm5kXe6Zl9A4OxdfjGdTb/E0rTgVtEnZ00g8bA68i//INHh9pU/QvmcXlM53QjRYm+36rPo8M5\niWde3F5o+mnfwCBzl32fQxbdwtxl3y/0qaJQwReRDuBfgHcAhwMfEpHDcz9RAZUyg0Q1gPNsGMfl\n4Of1tBLXks/zS1b0Y3A70zOTUjE/8974ev1RJHn+u8l/MNaC69LdRH617JTYJ6ne7i4eX3YK0yaO\nSfV+eYv+/MvuKGXUdZ7jDvoGBpl9wS2v9DkMbn4ZxbNXF37rZ4X9hopu4R8DPKqqv1TV7cA1wGl5\nnqDISpk+UZZO1PpWKDIH3yeuJZ/n9YrL0ElbMC2Odvn4SamYnaOy2VNJ3n+eg7HmX3ZH5BNekGZa\n76sWz29K9PMQyjkX3pppzEAe+MLfisd/xhV3JXYs79ilhWS5QfGC3wU8GVh+yq17BRE5R0RWi8jq\njRubz6xpR558O/oJiszB94nLjsmz4zYuQ6eZgmlRtMvHT/rRXfr+9OIYRhrvP4/BWGl9+zOPm9H0\nd60Z0b/zsU0tZ8D4GTitZBadedwMHl92ypC/yz94NC04cq/ge/xx4h9syc9cdEsqSw3if0NZKD0P\nX1WXA8sBenp6mjZjovLk8xTjonP9odgcfJ+FJx0a+Sjcro7bduSY5zW5edKPLo9znHncjNjskpdb\nyS8MsKRvbSqRyZKDvmrx/FRF4sDLgDl2aT+rFs9P/f6tjBcYJXDZB45OtKb87WdccVdqMQ7DF/8q\nFPiLo+gW/iAwPbB8kFuXGwtPOpRxnXumueVVKdOnHeUV2lH6IO7Ln9d52pXGVrSPn/Q5mknFjOOS\n3qMS6+y0aoX0DQymSlWcvf+EzAOO+hfMS93S9ztBk3x93/5oVuzPPG4Gv/xMfD9EIyvOPp7Hl52S\n2/9rVvKOrB0rAAARj0lEQVSwPsMoWvB/AswWkUNEZAxwOrAyzxP0dnfxmfceRdekcQiebfGZ9x6V\na0uyHbZRkTn4QYousVC0f+9TdJmFJDsnzxGZSYOx7nxsU0udnn97/c9S7de/YF7T7x1GM/YOeL7+\na88fWo7BF/pmW9zTJo7h8WWnZPq/uaT3KB5fdkpiFlXR5GF9hlGopaOqO0XkL4HbgA7ga6qae29E\n8NGsCNphGxWZgx+k6BILRfv3PkllFrLaOkV5qGH0dnfx6Rvui+0gvvrudfQcnFy+weeMK+6KLeHg\n00wnbRqasXfASxXOwwrJu0a//17NfJY8GDt6FJ9935zC9KzwPHxV/Y6qvl5VZ6nq0qLPVwTtKJEc\nlWufVw6+T5nTHeb9JS7K1klqTRfxuP3Z9yWnd6bNzW/Gty9CWPoXzGtbCzluzEAe+APOiv48fsfy\nw5e8o9DG67AfadsO2uHhR01eHrW+VYossRBnoxTRJVyUrZM0sraIx+3e7i7Gjo7/OabJzU/r28+d\nNaXQQmErzj6+cJGcvf+ExDEDeeF7/Hl+Jn8kc1YbqhlM8FMQ5dXnmcq4bUd4x1Telk6RJRbinhCK\nGMtbxOTmaUbWFiUwaVr5SX7+36R4ChhF+mJvWVhx9vGFdYJe/sGjc+t7aAZf+FsV/2CKaBnTRJae\nljkciPLwhXx8776BQV6KSL/L29KB4kosxN0A857hymfy+M5Iv70VW6eokbVp6O3u4vrV6xLtGL8F\nH2wV9g0MpvbB4+r2580lvUdxSe9RHLu0P5cyCFWaT7cqcTSDtfBTsPCkQ0MtCSUf3zvuPYqYO7eo\n6Q7jsgvzTJMNkmTrNJPdUvTI2jSsOPv4VAXKgiNXmykgVpRvn8SqxfMztfZn7z+htFZxnTDBT0Fv\nd1fkY34eqZlx71GEULZ7onQoTigTJx1pomRu0SNr05LWqvCH+KdtORft2yfhpzw2I/y+z12GfVNH\nzNJJSVeBqZmTImyJ8QX5xVGjerO078usGx5n6zRzC2vHyNq0JI3AbZZpE8dUpnXs2zxG+7EWfkqK\nTM2MaliPGR0/UUarJOXit0K7BlyFkUe2TrtG1qblkt6jmp50JIp9xnY0VcrAqC8m+CkpMjWzXTn4\nPnEdqK3Wk2/XgKswklreC69P9reTOmvLaJH2L5iXWfT3GdvBfRefnFNExnDHBD8lRZZXaFcOvk9c\nv0De9eShPVZI3CCsHbvjW/BJnbXtnQ9sT5qpUdPItIljTOyNPTDBT0mRs161q6yCTxlZGkWTZOvE\nlRlOqjlzRskFtVYtnt90S//M42aYjWMMwQQ/JUXOelX01IZhJBRobIp2j7ANo7e7K/ZcL+/YHRrn\nkr61iTVnqtDB6E8tmFS/3c9qqULMRvWwLJ2UFDXrlT+1YZjkFJGD7xOncc0OJmv3CNsozkjIbDn/\nxvuGfK6kTJiqlMuF4osEGvXHWvgpiRLfrJZOO6Y2DCPPImpljLANI6lV2ziZSJo689ZSNuqECX5K\nFp50KJ0hPsjW7Tsz5aC3Y2rDMPIsohZnpRR50wojaf6AY5f2A+kqSlapdW8YeWCCn5Le7i723muo\nA7ZjV7YJ04sqc5BE3M2kmTMnFRtrtwWR1Hn7zIvbOWzxd1INarLWvVE3TPCbIMqvz5Ka2Y6pDZul\nmTMXXUO/WdKUGf7truRPWPRgMcMoAxP8JigiNbNdUxs2S1qbKs7+KUs005QZTqLowWKGUQYm+E1Q\nRGpmu3Pwg8TdVNKOuI376GWJZm93V6YRqmVVlDSMoskk+CJyqYg8JCL3ichNIjIpsO18EXlURB4W\nkZOyh1o+RaRmlpGD7xPnd6cZcVs1/z5I/4J5LY01KLuipGEUSdYWfj9wpKrOAX4BnA8gIocDpwNH\nACcDXxKRYiqBtZG8LR0/Bz+MInPwfbIKctX8+0Yu+0Bz5Yxn7z+hMhUlDaMIMgm+qv6Xqu50i3cD\nB7nXpwHXqOo2Vf0V8ChwTJZzVYG8LZ2ycvCDxLWCk3z8Kvr3QXq7u7g85exO0yaOsZrrRu3J08P/\nKPBd97oLeDKw7Sm3bggico6IrBaR1Rs35jcpeBHkbemUlYMfJG7EbVILPu5mUZVOz97uLh5fdkqs\npz931hSrO2OMCBJLK4jI94DXhGxarKo3u30WAzuBFc0GoKrLgeUAPT095eUipiBqbttWLZ2oiUiK\nzsEPEjWxCyQPwCpjhqtWsda7YaRo4avqiap6ZMifL/YfAd4FnKH6inoNAtMDb3OQWzesyXu0bRVy\n8OOso7jbTpkzXBmG0RpZs3ROBs4D3q2qLwU2rQROF5GxInIIMBu4J8u5qkDeo22rkIMf1xKPu+0k\nzf9qGEb1yOrhfxGYCPSLyBoR+QqAqt4PXAc8ANwKfEJVd2U8VyXIc7RtmTn4aYlqycfNcFWFDlvD\nMIaSqTyyqr4uZttSYGmW968i+47rDM1Rb8XHLzMHPy2X3vbwkKeAJDunKh22hmHsiY20bZK8UjPL\nzsEPEmchhXXcJtk5VeuwNQzDwwS/SfJKzaxCDr5PUoXJRszOMYzhiQl+k+Q12rYKOfg+SecLWjhm\n5xjG8MUEv0nysnTKqoPfCsFCambnGMbwxQS/SaKsmzibI4wq5OAHifPxg53LZucYxvDFBL9JojpU\nheYGI1UhBz9Iko+/pG+t2TmGMcwxwW+ShScdGppdozRXPbJqOfhJVszVd6/j0zfcl+k9DMMoFxP8\nJunt7oocgdrM4KuoHPw0deiLIunpYtvO3ZHbzM4xjOpjgt8CUeLWTKZOFTttm03PDGJ2jmFUHxP8\nFsgjU6dqnbaQzZIxO8cwqo8JfgtkHXwVN8q2q82jbBtppdN4XKd9jQxjOGC/1BbIOviqSqNsG2nF\n1vnMe+cUEIlhGHljgt8CWS2dKo2ybaS3u4u5s6ak3n/urCmlx2wYRjpM8Fsgq6VTxQ7bICvOPp5p\nE8ck7jdt4hib9NswhhEm+C2Q1dKpYodtI6sWz2efsR2R2/fqEJsH1jCGGSb4LRDVEN++M90cL1Ub\nZRvFfRefHGrvzN5/Ag8tfWcJERmGkYVME6CMVKKsm5d27KZvYDDR0962I/zGUKEG/iuYZWMY9cFa\n+C0QN0FJUnmFvoFBXtoRPmK1SjNdGYZRP3IRfBH5lIioiOwXWHe+iDwqIg+LyEl5nKcqxKVOJpVX\niLshtHumK8MwRhaZBV9EpgN/CKwLrDscOB04AjgZ+JKIRPcADjN6u7sYHzHYKKnjNiolE8rPwTcM\no97k0cL/J+A82GMs0WnANaq6TVV/BTwKHJPDuSrD2M7w+1dSZmVU6qVQfg6+YRj1JpPgi8hpwKCq\n/qxhUxfwZGD5Kbcu7D3OEZHVIrJ648aNWcJpK63m4kelXlawv9YwjJqRmKUjIt8DXhOyaTFwAZ6d\n0zKquhxYDtDT0zNsdG/fcZ2hpYyTLJ3J4ztDZ42qWkqmYRj1I1HwVfXEsPUichRwCPAz8WyKg4Cf\nisgxwCAwPbD7QW5dbWg1F384pWQahlEvWrZ0VHWtqu6vqjNVdSaebfNGVX0aWAmcLiJjReQQYDZw\nTy4RV4SkXPwwLCXTMIwyKSQPX1XvB64DHgBuBT6hqumGoQ4TWsnFt5RMwzDKJDfBdy39ZwPLS1V1\nlqoeqqrfzes8VSEuhTIq9dJSMg3DKBMbadsivd1djIrw8ZuthmkpmYZhtAMT/AzsjuhobbYapvXX\nGobRDkzwMxA1mXnU+qrXwTcMo96Y4Geg2dTM4VAH3zCM+mKCn4FmUzOb9fwNwzDyxAQ/A82kZvYN\nDDbt+RuGYeSJCX4GmknNjMvB77IcfMMw2oAJfgaaSc20HHzDMMrGBD8jaW2aqBsDWA6+YRjtwQQ/\nI1E63rg+6sZgGIbRLkzwMxKl48H1UcXUDMMw2okJfoH4Qn/xt++P3CdqkJZhGEbemOBnJG7iEj8z\nJ2zCE5+L3n1E7jEZhmGEYYKfkQtPjRbsuMwcH+uwNQyjXZjgZyRJsOP8extfaxhGOzHBL5jFN62N\n3GaJO4ZhtBMT/ILZuj16oi8bYWsYRjvJLPgi8lci8pCI3C8i/xhYf76IPCoiD4vISVnPU2XiOm7j\nsBG2hmG0k0yCLyInAKcBb1DVI4DPufWHA6cDRwAnA18SkY6MsVaWuI7bOKzD1jCMdpK1hf/nwDJV\n3Qagqhvc+tOAa1R1m6r+CngUOCbjuSqLCbdhGMOBrIL/euAtIrJKRH4oIr/n1ncBTwb2e8qtG4KI\nnCMiq0Vk9caNGzOGM3ww/94wjHYzOmkHEfke8JqQTYvd8VOA44DfA64Tkdc2E4CqLgeWA/T09Azb\nxJXJ4ztjB1g1Yv69YRjtJrGFr6onquqRIX8347Xcb1SPe4DdwH7AIDA98DYHuXW1pVkf32wgwzDa\nTVZLpw84AUBEXg+MAZ4FVgKni8hYETkEmA3ck/Fclaa3u4uxo9NdTqufYxhGGWQV/K8BrxWRnwPX\nAGe51v79wHXAA8CtwCdUNTohvSZ89n1zUu1n9XMMwyiDTIKvqttV9Uxn8bxRVb8f2LZUVWep6qGq\n+t3soVafNDbN7P0nmJ1jGEYp2EjbnDnzuBmR2/YZ20H/gnntC8YwDCOACX7OXNJ7VKjoz501hfsu\nPrmEiAzDMDxEtTqZkD09Pbp69eqywzAMwxhWiMi9qtqTtJ+18A3DMEYIJviGYRgjBBN8wzCMEYIJ\nvmEYxgjBBN8wDGOEUKksHRHZCDyR4S32wyvtUFUsvuxUPUaLLztVj7GK8R2sqlOTdqqU4GdFRFan\nSU0qC4svO1WP0eLLTtVjrHp8cZilYxiGMUIwwTcMwxgh1E3wl5cdQAIWX3aqHqPFl52qx1j1+CKp\nlYdvGIZhRFO3Fr5hGIYRgQm+YRjGCKEWgi8iJ4vIwyLyqIgsKjueMETkcRFZKyJrRKT0kqAi8jUR\n2eBmK/PXTRGRfhF5xP07uWLxXSQig+4arhGRd5YY33QR+YGIPCAi94vIJ936Kl3DqBgrcR1FZC8R\nuUdEfubiu9itr8Q1jImvEtevFYa9hy8iHcAvgPl4k6r/BPiQqj5QamANiMjjQI+qVmLAhoi8FdgC\nfF1Vj3Tr/hHYpKrL3I1zsqp+ukLxXQRsUdXPlRFTEBE5ADhAVX8qIhOBe4Fe4CNU5xpGxfgBKnAd\nRUSACaq6RUQ6gR8BnwTeSwWuYUx8J1OB69cKdWjhHwM8qqq/VNXteHPrnlZyTJVHVf8b2NSw+jTg\nKvf6KjxxKIWI+CqDqq5X1Z+61y8CDwJdVOsaRsVYCdz811vcYqf7UypyDWPiG7bUQfC7gCcDy09R\noS91AAW+JyL3isg5ZQcTwTRVXe9ePw1MKzOYCP5KRO5zlk9pdkkQEZkJdAOrqOg1bIgRKnIdRaRD\nRNYAG4B+Va3UNYyIDypy/ZqlDoI/XHizqh4NvAP4hLMsKot6Xl/VWjNfBl4LHA2sBz5fbjggInsD\nNwDnquoLwW1VuYYhMVbmOqrqLve7OAg4RkSObNhe6jWMiK8y169Z6iD4g8D0wPJBbl2lUNVB9+8G\n4CY8K6pqPON8X9//3VByPHugqs+4H+Bu4ApKvobO170BWKGqN7rVlbqGYTFW7Tq6mDYDP8Dzxyt1\nDWHP+Kp4/dJSB8H/CTBbRA4RkTHA6cDKkmPaAxGZ4DrNEJEJwB8CP48/qhRWAme512cBN5cYyxB8\nEXC8hxKvoevQ+yrwoKpeFthUmWsYFWNVrqOITBWRSe71OLzEi4eoyDWMiq8q168Vhn2WDoBLi7oc\n6AC+pqpLSw5pD0TktXiteoDRwDfKjlFEvgnMwyv1+gxwIdAHXAfMwCtT/QFVLaXjNCK+eXiP0Qo8\nDnw84PW2O743A/8DrAV2u9UX4HnkVbmGUTF+iApcRxGZg9cp24HX+LxOVf9eRH6HClzDmPj+gwpc\nv1aoheAbhmEYydTB0jEMwzBSYIJvGIYxQjDBNwzDGCGY4BuGYYwQTPANwzBGCCb4hmEYIwQTfMMw\njBHC/wIn0DE+pyPI4gAAAABJRU5ErkJggg==\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x25bd22c3fd0>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "mat1 = np.array(ri(1,10,9)).reshape(3,3)\n",
    "mat2 = np.array(ri(1,10,9)).reshape(3,3)\n",
    "print(\"\\n1st Matrix of random single-digit numbers\\n----------------------------------------\\n\",mat1)\n",
    "print(\"\\n2nd Matrix of random single-digit numbers\\n----------------------------------------\\n\",mat2)\n",
    "\n",
    "print(\"\\nSq-root of 1st matrix using np\\n------------------\\n\", np.sqrt(mat1))\n",
    "print(\"\\nExponential power of 1st matrix using np\\n\",'-'*50,\"\\n\", np.exp(mat1))\n",
    "print(\"\\n10-base logarithm on 1st matrix using np\\n\",'-'*50,\"\\n\", np.log10(mat1))\n",
    "print(\"\\nModulo reminder using np\\n\",'-'*50,\"\\n\", np.fmod(mat1,mat2))\n",
    "\n",
    "print(\"\\nCombination of functions by shwoing exponetial decay of a sine wave\\n\",'-'*70)\n",
    "A = np.linspace(0,12*np.pi,1001)\n",
    "import matplotlib.pyplot as plt\n",
    "plt.scatter(x=A,y=100*np.exp(-A/10)*(np.sin(A)))\n",
    "plt.title(\"Exponential decay of sine wave: exp(-x)*sin(x)\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### NumPy basic statistics on array"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "1st Matrix of random single-digit numbers\n",
      " -------------------------------------------------- \n",
      " [[9 9 2]\n",
      " [4 1 1]\n",
      " [7 8 1]]\n",
      "\n",
      "2nd Matrix of random single-digit numbers\n",
      " -------------------------------------------------- \n",
      " [[9 3 6]\n",
      " [5 3 7]\n",
      " [5 8 2]]\n",
      "\n",
      "Sum of all numbers in 1st matrix\n",
      " -------------------------------------------------- \n",
      " 42\n",
      "\n",
      "Sum of all numbers in columns of 1st matrix\n",
      " -------------------------------------------------- \n",
      " [20 18  4]\n",
      "\n",
      "Sum of all numbers in rows of 1st matrix\n",
      " -------------------------------------------------- \n",
      " [20  6 16]\n",
      "\n",
      "Product of all numbers in rows of 1st matrix\n",
      " -------------------------------------------------- \n",
      " [162   4  56]\n",
      "\n",
      "Product of all numbers in columns of 2nd matrix\n",
      " -------------------------------------------------- \n",
      " [225  72  84]\n",
      "\n",
      "Mean of all numbers in 1st matrix\n",
      " -------------------------------------------------- \n",
      " 4.66666666667\n",
      "\n",
      "Standard deviation of all numbers in 1st matrix\n",
      " -------------------------------------------------- \n",
      " 3.36650164612\n",
      "\n",
      "Modified matrix of random numbers from 1 to 99\n",
      " -------------------------------------------------- \n",
      " [[17 50 33]\n",
      " [50 74 81]\n",
      " [16  7 55]]\n",
      "\n",
      "Standard deviation of all numbers in the modified matrix, a larger number\n",
      " -------------------------------------------------------------------------------- \n",
      " 24.5995081754\n",
      "\n",
      "Variance of all numbers in the modified matrix, a larger number\n",
      " -------------------------------------------------------------------------------- \n",
      " 605.135802469\n",
      "\n",
      "Median of all numbers in the modified matrix\n",
      " ------------------------------------------------------------ \n",
      " 50.0\n",
      "\n",
      "Modified matrix of 50 random numbers from 1 to 99\n",
      " -------------------------------------------------- \n",
      " [[38 85 87 90 26]\n",
      " [21 24 45 19 35]\n",
      " [81 41 93 78 28]\n",
      " [83 63 63  7 84]\n",
      " [19 50 59 19 89]\n",
      " [55 93 77 68 20]\n",
      " [53 38 91 81 97]\n",
      " [16 75 75 82 15]\n",
      " [40 28 73 95 95]\n",
      " [74 78 24 49 15]]\n",
      "\n",
      "Standard deviation along the columns in the modified matrix\n",
      " ------------------------------------------------------------ \n",
      " [ 24.12882094  23.39764945  20.58178807  31.16985723  33.97705108]\n",
      "\n",
      "Modified matrix of random numbers from 1 to 49\n",
      " -------------------------------------------------- \n",
      " [[10 96 78 26 10]\n",
      " [26 82  9 83 35]\n",
      " [26  3 95 29 17]\n",
      " [30 42 58 87 58]]\n",
      "\n",
      "Flattened and sorted matrix (as vector)\n",
      " -------------------------------------------------- \n",
      " [[ 3  9 10 10 17 26 26 26 29 30 35 42 58 58 78 82 83 87 95 96]]\n",
      "\n",
      "50th percentile of all numbers in the modified matrix\n",
      " ------------------------------------------------------------ \n",
      " 32.5\n",
      "\n",
      "90th percentile of all numbers in the modified matrix\n",
      " ------------------------------------------------------------ \n",
      " 87.8\n"
     ]
    }
   ],
   "source": [
    "mat1 = np.array(ri(1,10,9)).reshape(3,3)\n",
    "mat2 = np.array(ri(1,10,9)).reshape(3,3)\n",
    "print(\"\\n1st Matrix of random single-digit numbers\\n\",\"-\"*50,\"\\n\",mat1)\n",
    "print(\"\\n2nd Matrix of random single-digit numbers\\n\",\"-\"*50,\"\\n\",mat2)\n",
    "\n",
    "print(\"\\nSum of all numbers in 1st matrix\\n\",\"-\"*50,\"\\n\",np.sum(mat1))\n",
    "print(\"\\nSum of all numbers in columns of 1st matrix\\n\",\"-\"*50,\"\\n\",np.sum(mat1,axis=0))\n",
    "print(\"\\nSum of all numbers in rows of 1st matrix\\n\",\"-\"*50,\"\\n\",np.sum(mat1,axis=1))\n",
    "print(\"\\nProduct of all numbers in rows of 1st matrix\\n\",\"-\"*50,\"\\n\",np.prod(mat1,axis=1))\n",
    "print(\"\\nProduct of all numbers in columns of 2nd matrix\\n\",\"-\"*50,\"\\n\",np.prod(mat2,axis=0))\n",
    "print(\"\\nMean of all numbers in 1st matrix\\n\",\"-\"*50,\"\\n\",np.mean(mat1))\n",
    "print(\"\\nStandard deviation of all numbers in 1st matrix\\n\",\"-\"*50,\"\\n\",np.std(mat1))\n",
    "\n",
    "mat1 = np.array(ri(1,100,9)).reshape(3,3)\n",
    "print(\"\\nModified matrix of random numbers from 1 to 99\\n\",\"-\"*50,\"\\n\",mat1)\n",
    "print(\"\\nStandard deviation of all numbers in the modified matrix, a larger number\\n\",\"-\"*80,\"\\n\",np.std(mat1))\n",
    "print(\"\\nVariance of all numbers in the modified matrix, a larger number\\n\",\"-\"*80,\"\\n\",np.var(mat1))\n",
    "print(\"\\nMedian of all numbers in the modified matrix\\n\",\"-\"*60,\"\\n\",np.median(mat1))\n",
    "\n",
    "mat2 = np.array(ri(1,100,50)).reshape(10,5)\n",
    "print(\"\\nModified matrix of 50 random numbers from 1 to 99\\n\",\"-\"*50,\"\\n\",mat2)\n",
    "print(\"\\nStandard deviation along the columns in the modified matrix\\n\",\"-\"*60,\"\\n\",np.std(mat2,axis=0))\n",
    "\n",
    "mat1 = np.array(ri(1,100,20)).reshape(4,5)\n",
    "print(\"\\nModified matrix of random numbers from 1 to 49\\n\",\"-\"*50,\"\\n\",mat1)\n",
    "print(\"\\nFlattened and sorted matrix (as vector)\\n\",\"-\"*50,\"\\n\",np.sort(mat1.reshape(1,20)))\n",
    "print(\"\\n50th percentile of all numbers in the modified matrix\\n\",\"-\"*60,\"\\n\",np.percentile(mat1,50))\n",
    "print(\"\\n90th percentile of all numbers in the modified matrix\\n\",\"-\"*60,\"\\n\",np.percentile(mat1,90))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Correlation and covariance"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 43,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "B is twice that of A plus some random noise\n"
     ]
    },
    {
     "data": {
      "image/png": "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\nrW6Fi6YeLWPWuZzcrSFLFw85mZt1MHfLmJnlkM/czXLCPyyzYk7uZjngH5ZZKXfLmOWAf1hmpZzc\nzXLAPyyzUk0nd0k9kjZKur4VAZlZ/fzDMivVijP39wObW7AdM2uQf1hmpZpK7pLmAm8GLmlNOGbW\niKWLh1i1bBFD/X0IGOrvY9WyRb6Y+gLW7GiZi4APAbNbEIt1EQ+76zz+YZkVa/jMXdJpwGMRcUeV\n5ZZLGpU0Oj4+3ujurIMUht2NTUwS7B52N7JxrN2hmVnSTLfMicBbJD0AXAW8QdK60oUiYk1EDEfE\n8MDAQBO7s07hYXdmna/h5B4RKyNibkQsAM4AboqIt7UsMutYHnZn1vk8zt3q5mF3Zp2vJck9Im6J\niNNasS3rfB52Z9b5fG8Zq5vv527W+ZzcrSEedmfW2dznbmaWQ07uZmY55ORuZpZDTu5mZjnk5G5m\nlkNO7mZmOeTkbmaWQ07uZmY55ORuZpZDTu5mZjnk5G5mlkNO7mZmOeTkbmaWQ07uZmY55ORuZpZD\nTu5mZjnk5G5mlkMNP4lJ0jzgcuBQIIA1EfG5VgVWbGTjmB/pZmZWh2Yes/c8cF5E3ClpNnCHpBsi\n4r4WxQZkiX3l+k1M7tgJwNjEJCvXbwJwgjczq6DhbpmIeDgi7kyvnwY2Ay3Ptqs3bNmV2Asmd+xk\n9YYtrd6VmVlutKTPXdICYDFwe5l5yyWNShodHx+ve9vbJybrmm5mZi1I7pIOBK4F/iIiniqdHxFr\nImI4IoYHBgbq3n7//r11TTczsyaTu6RessR+RUSsb01Ie4qob7qZmTU3WkbAV4DNEfGZ1oW0pycn\nd9Q13WaGRzCZdbZmztxPBN4OvEHSXenv1BbFtctgf19d0236FUYwjU1MEuwewTSycazdoZlZ0sxo\nme9HhCLimIh4Zfr7diuDA1ixZCF9vT17TOvr7WHFkoWt3pXVyCOYzDpfM+PcZ0Thv/ruAugcHsFk\n1vk6PrlDluCdzDvHYH8fY2USubvKzDqH7y1jdXNXWWca2TjGiRfexBHnf4sTL7zJ10Be4LrizN06\ni7vKOo9v02GlnNytIe4q6yxTXeR2Pb0wuVvGLAd8kdtKObmb5YB/D2KlnNzNcsAXua2U+9zNcsAX\nua2Uk7tZTvgitxVzt4yZWQ45uZuZ5ZCTu5lZDrnP3SwnfI99K+bkbpYDvv2AlXK3jFkO+B77VsrJ\n3SwHfPsBK+XkbpYDvv2AlXJyN8sB337ASjWV3CWdImmLpPslnd+qoMysPksXD7Fq2SKG+vsQMNTf\nx6pli3wx9QWs4dEyknqA/wmcDGwDfiTpuoi4r1XBmVntfPsBK9bMmftxwP0R8fOIeA64Cji9NWGZ\nmVkzmhnnPgRsLXq/DTi+dCFJy4HlAPPnz29id9ZJ/IMZs8427RdUI2JNRAxHxPDAwMB0785mQOEH\nM2MTkwS7fzDjBzKbdY5mkvsYMK/o/dw0zXLOP5gx63zNJPcfAS+XdISkfYEzgOtaE5Z1Mv9gxqzz\nNZzcI+J54L3ABmAzcHVE3NuqwKxz+QczZp2vqT73iPh2RPz7iHhZRHy6VUFZZ/MPZsw6n+8KaXXz\n8zrNOp+TuzXEP5gx62y+t4yZWQ45uZuZ5ZCTu5lZDjm5m5nlkJO7mVkOKSJmbmfSOPBgE5uYAzze\nonDaKS/lgPyUJS/lgPyUJS/lgObLcnhE1HVzrhlN7s2SNBoRw+2Oo1l5KQfkpyx5KQfkpyx5KQe0\npyzuljEzyyEndzOzHOq25L6m3QG0SF7KAfkpS17KAfkpS17KAW0oS1f1uZuZWW267czdzMxq4ORu\nZpZDHZfcJV0q6TFJ91SYL0mfl3S/pB9LOnamY6xVDWU5SdKTku5Kfx+b6RhrIWmepJsl3SfpXknv\nL7NMx9dLjeXoljqZJemHku5OZflEmWW6oU5qKUdX1AmApB5JGyVdX2bezNZHRHTUH/A64Fjgngrz\nTwW+Awg4Abi93TE3UZaTgOvbHWcN5TgMODa9ng38FDiq2+qlxnJ0S50IODC97gVuB07owjqppRxd\nUScp1g8CXy8X70zXR8eduUfErcATUyxyOnB5ZG4D+iUdNjPR1aeGsnSFiHg4Iu5Mr58me6xi6c3c\nO75eaixHV0jH+Zn0tjf9lY6O6IY6qaUcXUHSXODNwCUVFpnR+ui45F6DIWBr0fttdOkHNHlN+i/a\ndyS9ot3BVCNpAbCY7AyrWFfVyxTlgC6pk9QFcBfwGHBDRHRlndRQDuiOOrkI+BDw2wrzZ7Q+ujG5\n58mdwPyIOAb4AjDS5nimJOlA4FrgLyLiqXbH06gq5eiaOomInRHxSmAucJyko9sdUyNqKEfH14mk\n04DHIuKOdsdS0I3JfQyYV/R+bprWdSLiqcJ/SSPi20CvpDltDqssSb1kCfGKiFhfZpGuqJdq5eim\nOimIiAngZuCUklldUScFlcrRJXVyIvAWSQ8AVwFvkLSuZJkZrY9uTO7XAe9IV55PAJ6MiIfbHVQj\nJP2OJKXXx5HVxy/bG9XeUoxfATZHxGcqLNbx9VJLObqoTgYk9afXfcDJwE9KFuuGOqlajm6ok4hY\nGRFzI2IBcAZwU0S8rWSxGa2PjntAtqQrya6Oz5G0Dfg42UUWIuJi4NtkV53vB34NvLM9kVZXQ1ne\nCvyppOeBSeCMSJfVO8yJwNuBTalvFOAjwHzoqnqppRzdUieHAZdJ6iFLdldHxPWSzoWuqpNaytEt\ndbKXdtaHbz9gZpZD3dgtY2ZmVTi5m5nlkJO7mVkOObmbmeWQk7uZWQ45uZuZ5ZCTu5lZDv1/2YdD\nlj9B6ooAAAAASUVORK5CYII=\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x25bd408e6d8>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[ 1.          0.55638699]\n",
      " [ 0.55638699  1.        ]]\n",
      "\n",
      "B is 100 minus twice that of A plus some random noise\n"
     ]
    },
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x25bd4028320>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "[[ 1.         -0.94973377]\n",
      " [-0.94973377  1.        ]]\n"
     ]
    }
   ],
   "source": [
    "A = ri(1,5,20) # 20 random integeres from a small range (1-10)\n",
    "B = 2*A+5*np.random.randn(20) # B is twice that of A plus some random noise\n",
    "print(\"\\nB is twice that of A plus some random noise\")\n",
    "plt.scatter(A,B) # Scatter plot of B\n",
    "plt.title(\"Scatter plot of A vs. B, expect a small positive correlation\")\n",
    "plt.show()\n",
    "print(np.corrcoef(A,B)) # Correleation coefficient matrix between A and B\n",
    "\n",
    "A = ri(1,50,20) # 20 random integeres from a larger range (1-50)\n",
    "B = 100-2*A+10*np.random.randn(20) # B is 100 minus twice that of A plus some random noise\n",
    "print(\"\\nB is 100 minus twice that of A plus some random noise\")\n",
    "plt.scatter(A,B) # Scatter plot of B\n",
    "plt.title(\"Scatter plot of A vs. B, expect a large negative correlation\")\n",
    "plt.show()\n",
    "print(\"\")\n",
    "print(np.corrcoef(A,B)) # Correleation coefficient matrix between A and B"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Linear Algebra Operations"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Dot/Inner/Outer products"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "1st Matrix of 1-9 single-digit numbers (A)\n",
      " -------------------------------------------------- \n",
      " [[1 2 3]\n",
      " [4 5 6]\n",
      " [7 8 9]]\n",
      "\n",
      "2nd Matrix of random single-digit numbers (B)\n",
      " -------------------------------------------------- \n",
      " [[3 1 6]\n",
      " [3 3 2]\n",
      " [7 9 7]]\n",
      "\n",
      "Dot product of A and B (for 2D arrays it is equivalent to matrix multiplication) \n",
      " -------------------------------------------------------------------------------- \n",
      " [[ 30  34  31]\n",
      " [ 69  73  76]\n",
      " [108 112 121]]\n",
      "\n",
      "1st Vector of 1-5 numbers (A)\n",
      " -------------------------------------------------- \n",
      " [1 2 3 4 5]\n",
      "\n",
      "2nd Vector of 5 random single-digit numbers (B)\n",
      " -------------------------------------------------- \n",
      " [2 5 9 2 3]\n",
      "\n",
      "Inner product of vectors A and B \n",
      " -------------------------------------------------- \n",
      " 62 (sum of all pairwise elements)\n",
      "\n",
      "Outer product of vectors A and B \n",
      " -------------------------------------------------- \n",
      " [[ 2  5  9  2  3]\n",
      " [ 4 10 18  4  6]\n",
      " [ 6 15 27  6  9]\n",
      " [ 8 20 36  8 12]\n",
      " [10 25 45 10 15]]\n"
     ]
    }
   ],
   "source": [
    "A = np.arange(1,10).reshape(3,3)\n",
    "B = ri(1,10,9).reshape(3,3)\n",
    "print(\"\\n1st Matrix of 1-9 single-digit numbers (A)\\n\",\"-\"*50,\"\\n\",A)\n",
    "print(\"\\n2nd Matrix of random single-digit numbers (B)\\n\",\"-\"*50,\"\\n\",B)\n",
    "\n",
    "print(\"\\nDot product of A and B (for 2D arrays it is equivalent to matrix multiplication) \\n\",\"-\"*80,\"\\n\",np.dot(A,B))\n",
    "\n",
    "A = np.arange(1,6)\n",
    "B = ri(1,10,5)\n",
    "print(\"\\n1st Vector of 1-5 numbers (A)\\n\",\"-\"*50,\"\\n\",A)\n",
    "print(\"\\n2nd Vector of 5 random single-digit numbers (B)\\n\",\"-\"*50,\"\\n\",B)\n",
    "\n",
    "print(\"\\nInner product of vectors A and B \\n\",\"-\"*50,\"\\n\",np.inner(A,B), \"(sum of all pairwise elements)\")\n",
    "print(\"\\nOuter product of vectors A and B \\n\",\"-\"*50,\"\\n\",np.outer(A,B))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Transpose"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 45,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "3x3 Matrix of random single-digit numbers\n",
      " -------------------------------------------------- \n",
      " [[6 7 5]\n",
      " [1 2 7]\n",
      " [3 7 2]]\n",
      "\n",
      "Matrix transpose\n",
      " -------------------------------------------------- \n",
      " [[6 1 3]\n",
      " [7 2 7]\n",
      " [5 7 2]]\n",
      "\n",
      "3x2 Matrix of random single-digit numbers\n",
      " -------------------------------------------------- \n",
      " [[8 4]\n",
      " [1 9]\n",
      " [2 3]]\n",
      "\n",
      "2x3 Matrix transpose\n",
      " -------------------------------------------------- \n",
      " [[8 1 2]\n",
      " [4 9 3]]\n",
      "\n",
      "Matrix multiplication of B and B-transpose\n",
      " -------------------------------------------------- \n",
      " [[80 44 28]\n",
      " [44 82 29]\n",
      " [28 29 13]]\n"
     ]
    }
   ],
   "source": [
    "A = ri(1,10,9).reshape(3,3)\n",
    "print(\"\\n3x3 Matrix of random single-digit numbers\\n\",\"-\"*50,\"\\n\",A)\n",
    "print(\"\\nMatrix transpose\\n\",\"-\"*50,\"\\n\",np.transpose(A))\n",
    "\n",
    "B = ri(1,10,6).reshape(3,2)\n",
    "print(\"\\n3x2 Matrix of random single-digit numbers\\n\",\"-\"*50,\"\\n\",B)\n",
    "print(\"\\n2x3 Matrix transpose\\n\",\"-\"*50,\"\\n\",np.transpose(B))\n",
    "print(\"\\nMatrix multiplication of B and B-transpose\\n\",\"-\"*50,\"\\n\",np.dot(B, np.transpose(B)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Trace"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 46,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    },
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "4x4 Matrix of random single-digit numbers\n",
      " -------------------------------------------------- \n",
      " [[9 4 1 9]\n",
      " [2 4 8 6]\n",
      " [8 7 5 9]\n",
      " [9 4 4 7]]\n",
      "\n",
      "Matrix trace\n",
      " -------------------------------------------------- \n",
      " 25\n",
      "\n",
      "Matrix trace with ofset +1 (upper triangle)\n",
      " -------------------------------------------------- \n",
      " 21\n",
      "\n",
      "Matrix trace with ofset -1 (lower triangle)\n",
      " -------------------------------------------------- \n",
      " 13\n"
     ]
    }
   ],
   "source": [
    "A = ri(1,10,16).reshape(4,4)\n",
    "print(\"\\n4x4 Matrix of random single-digit numbers\\n\",\"-\"*50,\"\\n\",A)\n",
    "print(\"\\nMatrix trace\\n\",\"-\"*50,\"\\n\",np.trace(A))\n",
    "print(\"\\nMatrix trace with ofset +1 (upper triangle)\\n\",\"-\"*50,\"\\n\",np.trace(A,offset=1))\n",
    "print(\"\\nMatrix trace with ofset -1 (lower triangle)\\n\",\"-\"*50,\"\\n\",np.trace(A,offset=-1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Singular value decomposition (SVD)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original matrix\n",
      " [[5 6 2]\n",
      " [3 1 7]\n",
      " [8 8 7]]\n",
      "\n",
      "\n",
      "u: [[-0.45772212  0.52841166 -0.71503257]\n",
      " [-0.37892562 -0.84346758 -0.38075953]\n",
      " [-0.80430457  0.0966621   0.58630248]]\n",
      "\n",
      "\n",
      "Singular values, s: [ 16.52900847   5.27168794   0.03442903]\n",
      "\n",
      "\n",
      "v: [[-0.59651636 -0.57835864 -0.55647957]\n",
      " [ 0.16786888  0.5881037  -0.7911726 ]\n",
      " [-0.7848492   0.565363    0.25372506]]\n",
      "\n",
      "\n",
      "Reconstruction of A, u*s*v\n",
      " [[ 5.  6.  2.]\n",
      " [ 3.  1.  7.]\n",
      " [ 8.  8.  7.]]\n"
     ]
    }
   ],
   "source": [
    "A = np.random.randint(1,10,9).reshape(3,3)\n",
    "print(\"Original matrix\\n\",A)\n",
    "print(\"\\n\")\n",
    "u,s,v=np.linalg.svd(A,compute_uv=1,full_matrices=True)\n",
    "print(\"u:\",u)\n",
    "print(\"\\n\")\n",
    "print(\"Singular values, s:\",s)\n",
    "print(\"\\n\")\n",
    "print(\"v:\",v)\n",
    "print(\"\\n\")\n",
    "print (\"Reconstruction of A, u*s*v\\n\",np.dot(u,np.dot(np.diag(s),v)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### QR decomposition/factorization"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original matrix\n",
      " [[3 7 4]\n",
      " [5 5 9]\n",
      " [3 8 1]]\n",
      "\n",
      "\n",
      "Q: [[-0.45749571  0.43156567 -0.77746302]\n",
      " [-0.76249285 -0.64023479  0.09329556]\n",
      " [-0.45749571  0.63549231  0.62197041]]\n",
      "\n",
      "\n",
      "R: [[ -6.55743852 -10.67489992  -9.14991422]\n",
      " [  0.           4.90372426  -3.40035812]\n",
      " [  0.           0.          -1.6482216 ]]\n",
      "\n",
      "\n",
      "Reconstruction of A, Q*R\n",
      " [[ 3.  7.  4.]\n",
      " [ 5.  5.  9.]\n",
      " [ 3.  8.  1.]]\n"
     ]
    }
   ],
   "source": [
    "A = np.random.randint(1,10,9).reshape(3,3)\n",
    "print(\"Original matrix\\n\",A)\n",
    "print(\"\\n\")\n",
    "q,r=np.linalg.qr(A)\n",
    "print(\"Q:\",q)\n",
    "print(\"\\n\")\n",
    "print(\"R:\",r)\n",
    "print(\"\\n\")\n",
    "print (\"Reconstruction of A, Q*R\\n\",np.dot(q,r))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Eigenvalues and eigenvectors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 95,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Original matrix\n",
      " [[ 0.05594554  1.02096149  0.94962784]\n",
      " [-0.94312813 -0.13377838 -0.19325112]\n",
      " [ 0.61866049  0.84990374 -0.45748733]]\n",
      "\n",
      "\n",
      "Eigenvalues:\n",
      " [ 0.26958701+1.03654742j  0.26958701-1.03654742j -1.07449419+0.j        ]\n",
      "\n",
      "\n",
      "Eigenvectors:\n",
      " [[ 0.65609706+0.j          0.65609706-0.j         -0.45466685+0.j        ]\n",
      " [-0.24271546+0.57868435j -0.24271546-0.57868435j -0.28229792+0.j        ]\n",
      " [ 0.40855234+0.09399606j  0.40855234-0.09399606j  0.84474016+0.j        ]]\n"
     ]
    }
   ],
   "source": [
    "A = np.random.randn(9).reshape(3,3)\n",
    "print(\"Original matrix\\n\",A)\n",
    "print(\"\\n\")\n",
    "w,v=np.linalg.eig(A)\n",
    "print(\"Eigenvalues:\\n\",w)\n",
    "print(\"\\n\")\n",
    "print(\"Eigenvectors:\\n\",v)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Linear equation solving, matrix inverse, linear least suqare\n",
    "\n",
    "You can directly solve linear system of equations using NumPy writing only one line of code.\n",
    "\n",
    "Suppose you want to solve the following set of equations for (x,y,z). It is easy to see that (2,1,5) are the unique solutions.\n",
    "\n",
    "2*x* + 5*y* + *z* = 14;\n",
    "\n",
    "3*x* - 2*y* - *z* = -1;\n",
    "\n",
    "*x* - 3*y* + *z* = 4"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 100,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "The solutions are: [ 2.  1.  5.]\n"
     ]
    }
   ],
   "source": [
    "A = np.array([[2,5,1],[3,-2,-1],[1,-3,1]])\n",
    "B = np.array([14,-1,4])\n",
    "x = np.linalg.solve(A,B)\n",
    "\n",
    "print(\"The solutions are:\",x)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "** Linear least squares can be calculated easily as a solution to a linear regression problem **"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 133,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[ 1  2  3  4  5  6  7  8  9 10]\n",
      "[  2.2186607    2.53379561   4.65245542   7.24311354   8.67462021\n",
      "  10.25717262  13.6653078   15.34900931  16.59674539  18.72669168]\n"
     ]
    }
   ],
   "source": [
    "x = np.arange(1,11,1)\n",
    "y = 2*x+np.random.randn(10)-1\n",
    "print(x)\n",
    "print(y)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 135,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[[  1.   1.]\n",
      " [  2.   1.]\n",
      " [  3.   1.]\n",
      " [  4.   1.]\n",
      " [  5.   1.]\n",
      " [  6.   1.]\n",
      " [  7.   1.]\n",
      " [  8.   1.]\n",
      " [  9.   1.]\n",
      " [ 10.   1.]]\n"
     ]
    }
   ],
   "source": [
    "A = np.vstack([x, np.ones(len(x))]).T\n",
    "print(A)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 136,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Coefficient:1.94754443612\n",
      "intercept:-0.719737172139\n"
     ]
    },
    {
     "data": {
      "image/png": "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\nzyrbhVjZx/O9HKBSytdVeDmnMWYncJmb428UuW2A+yp6DnWadetgyBBITobYWJg0CZo0ASA0JIis\n3NKTemgtvWZPKX+nLRuqg9xcePJJiIqCPXvsVogffHAy6QPERjYhKEBKfJqgAOGGSG3EppS/08Tv\n6775BiIj4dln4bbbYPNmuPlmkFOT/LDolgQHlvzfGRwYwNDoMG9Gq5SqBjTx+6qsLBg9Grp3tw3W\nPv0U3nkHGjRwO/yiBqFMHtSROsGBZ1T+QQFCneBAJg/qyEUNQqsieqWUD9PE74uWLYP27eHVV+2q\nnY0bISam1If1aN2IpaOjGdi5OXVDghCBuiFBDOzcnKWjo+nRulEVBK+U8nViP3/1LVFRUSYp6Yye\nbzXfr7/CQw/BW2/Bn/8M06dDdLTTUSmlqgERSTbGRJVlrFb8viI+HsLD7XTOo4/aFTya9JVSXqBr\n+5x24ACMHGlX6kREwCef2C0RlVLKS7Tid4oxMGsWtGljWyg/95zdFlGTvlLKy7Tid0JaGtxzD3z2\nGXTrZq++vfRSp6NSSvkJrfirUmGhvdq2XTv46it4/XVITNSkr5SqUlrxV5WtW2HoUJvwr70W3nwT\nWrRwOiqllB/Sit/b8vLg+efhsstg0yaYOROWLtWkr5RyjFb83pSaapuqpabaNguvvw5/+pPTUSml\n/JxW/N7w++/w2GNw+eWwbx8sXGiXa2rSV0r5AK34Pe3rr22Vv3Ur3HUXvPQSnHuu01EppdRJWvF7\nytGj9kKs6Ghb8X/2mW29oElfKeVjNPF7wmef2SWakybZ5L9xo125o5RSPkineirjl1/gn/+0/XUu\nvZSfP1nGxNxGLBr/Ndm5+YSGBBEb2YRh0S21HbJSymdoxV9RCxbYdgtz5sDjj7Pqvc/osTqPeWv2\nkJWbjwGycvOZt2YPMRMSSdh60OmIlVIKqETiF5ELRSRBRDaLyCYRGeVmzFUickRE1rq+nqpcuD5g\n/3646Sa45RZo1gy+/560B/7FvfM3k5NXQH7hqW2u8wsNOXkFxM1OIS0z26GglVLqD5Wp+POBB40x\n4cAVwH0iEu5mXKIxJsL19Z9KnM9ZxsDbb9vWyZ98Yi/K+u47iIhgWuJO8goKS3x4XkEh0xN3VVGw\nSilVvArP8Rtj9gP7XbePisgWoCmw2UOxlVlaZjbTEneyKHWfd+bWd++G4cPtzljR0XaDlD//+eTd\n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\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x25bd4927e10>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "m, c = np.linalg.lstsq(A, y)[0]\n",
    "print (\"Coefficient:\"+str(m)+ \"\\n\"+\"intercept:\"+str(c))\n",
    "\n",
    "# Plot the fitteed line\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "plt.plot(x, y, 'o', label='Original data', markersize=10)\n",
    "plt.plot(x, m*x + c, 'r', label='Fitted line')\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "** Both normal multiplicative and *Moore-Penrose* pseudo-inverse matrix (for generalized *m* x *n* array) can be calculated **"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 137,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "A:\n",
      " [[ 1.3  0.1  1.8  1.6]\n",
      " [ 1.5  1.4  0.1  1.2]\n",
      " [ 0.4  1.6  1.1  0.7]\n",
      " [ 1.4  1.6  1.5  1.2]]\n",
      "Inverse of A:\n",
      " [[-0.54152908 -0.1976124  -1.71094742  1.91770384]\n",
      " [-0.34188468  0.12852426  0.49580899  0.03810008]\n",
      " [ 0.01016002 -0.74676149 -0.19304039  0.84582169]\n",
      " [ 1.07493015  0.99263399  1.57632715 -2.51206502]]\n"
     ]
    }
   ],
   "source": [
    "A = 0.1*np.random.randint(1,20,16).reshape(4,4)\n",
    "print (\"A:\\n\",A)\n",
    "print (\"Inverse of A:\\n\",np.linalg.inv(A))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 138,
   "metadata": {
    "collapsed": false,
    "jupyter": {
     "outputs_hidden": false
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "A:\n",
      " [[ 1.3  1.9  0.7]\n",
      " [ 0.5  0.3  1.5]\n",
      " [ 0.5  1.8  1.3]\n",
      " [ 1.6  1.9  0.3]\n",
      " [ 0.9  0.3  1.2]]\n",
      "Pseudo-inverse of A:\n",
      " [[ 0.10887279  0.06388003 -0.68929921  0.45682289  0.48917593]\n",
      " [ 0.14467469 -0.18830267  0.49458998  0.00372232 -0.38575162]\n",
      " [-0.07582968  0.36975047  0.24662895 -0.24944669  0.21055954]]\n",
      "Matrix product of A and pseudo inverse:\n",
      " [[  1.00000000e+00  -5.04407213e-17   2.42326325e-16]\n",
      " [ -6.40765579e-17   1.00000000e+00  -1.30121024e-17]\n",
      " [ -4.25091176e-17   1.78659733e-16   1.00000000e+00]]\n"
     ]
    }
   ],
   "source": [
    "A = 0.1*np.random.randint(1,20,15).reshape(5,3)\n",
    "print (\"A:\\n\",A)\n",
    "print (\"Pseudo-inverse of A:\\n\",np.linalg.pinv(A))\n",
    "print (\"Matrix product of A and pseudo inverse:\\n\", np.dot(np.linalg.pinv(A),A))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
